﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace vulnanalyzer
{
    class FolderMaster
    {
        private DirectoryInfo directory;
        private static String incompleteDirectory;


        /// <summary>
        /// конструкторы
        /// </summary>
        static FolderMaster()
        {
            incompleteDirectory = @"./Incomplete";
        }

        public FolderMaster(DirectoryInfo directory)
        {
            this.directory = directory;
        }

        public FolderMaster(Loading item)
        {
            directory = new DirectoryInfo(String.Format("{0}/inc_{1}", incompleteDirectory, item.FileName));
        }


        /// <summary>
        /// Чистит исходный каталог от всех каталогов именя которых не начинаются с inc_ ,удаляет все подкаталоги оставшихся, а также оставляет только файлы *_part или Loading_*
        /// </summary>
        public static void Clear()
        {
            AddIncompleteDirectory();

            //удаляет информацию обо всех каталогах не для закачки
            List<DirectoryInfo> noDownloadFolder = GetNoDownloadingCommonFolder();
            foreach (DirectoryInfo a in noDownloadFolder)
            {
                a.Delete(true);
            }


            List<DirectoryInfo> downloadFolder = GetDownloadingCommonFolder();
            foreach (DirectoryInfo a in downloadFolder)
            {
                //удаляет все файлы, которые не являются частями.
                foreach (FileInfo fi in a.GetFiles())
                {
                    if (!IsItSignatureFile(fi, GetNameOfLoadingStatic(a.Name)))
                        fi.Delete();
                }
                //удаляет все подкаталоги
                foreach (DirectoryInfo di in a.GetDirectories())
                {
                    di.Delete(true);
                }
            }
        }

        /// <summary>
        /// Собирает все части в один файл и удаляет каталог
        /// </summary>
        public void BuildAllFile()
        {
            FileInfo[] allFiles = GetAllPartsOfFile();
            List<int> allFilesWithNumbers = new List<int>();

            foreach (FileInfo fi in allFiles)
            {
                allFilesWithNumbers.Add(GetNumberOfPart(fi));
            }
            allFilesWithNumbers.Sort();

            Downloader down = new Downloader(ReadInitializingFile());

            FileInfo mainDownloadingFile = new FileInfo(String.Format("{0}.xml", GetNameOfLoading())); //программно определить расширение файла
            BinaryWriter bw = new BinaryWriter(mainDownloadingFile.OpenWrite());

            foreach (int number in allFilesWithNumbers)
            {
                FileInfo currentFile = GetFileInfoByNumber(number);
                BinaryReader br = new BinaryReader(currentFile.OpenRead());

                int bufLen = 2048;
                int cnt;

                do
                {
                    byte[] bt = new byte[bufLen];
                    cnt = br.Read(bt, 0, bufLen);
                    if (cnt != 0)
                    {
                        bw.Write(bt, 0, cnt);
                    }
                }
                while (cnt != 0);

                br.Close();
            }
            bw.Close();

            FolderMaster.RemoveDownloadFolder(GetNameOfLoading());
        }

        /// <summary>
        /// Возвращяет список указывающий на все скачанные части файла
        /// </summary>
        /// <returns></returns>
        private FileInfo[] GetAllPartsOfFile()
        {
            String fileName = directory.Name.Remove(0, 4);
            FileInfo[] files = directory.GetFiles(String.Format("{0}_part*.bin", fileName));
            return files;
        }


        /// <summary>
        /// Создается новый подкаталог для загрузки, а также файл информации о скачиваемом объекте
        /// </summary>
        /// <param name="current"></param>
        public static void AddNewDownloadFolder(Loading current)
        {
            AddNewSubFolder(current.FileName);
            WriteInitializeFileStatic(current, current.FileName);
        }

        public static void AddIncompleteDirectory()
        {
            DirectoryInfo currDir = new DirectoryInfo("Incomplete");
            currDir.Create();
        }

        /// <summary>
        /// создание подпапки inc_ в текущей папке
        /// </summary>
        /// <param name="directoryNameWithoutInc">имя папки, к которому добавится спереди inc_</param>
        private static void AddNewSubFolder(String directoryNameWithoutInc)
        {
            DirectoryInfo currDir = new DirectoryInfo(String.Format("{0}/inc_{1}", incompleteDirectory, directoryNameWithoutInc));
            currDir.Create();
        }

        /// <summary>
        /// Удаляет папку с directoryNameWithoutInc текущую папку из каталога incompleteDirectory
        /// </summary>
        /// <param name="directoryNameWithoutInc"></param>
        public static void RemoveDownloadFolder(String directoryNameWithoutInc)
        {
            DirectoryInfo currDir = new DirectoryInfo(String.Format("{0}/inc_{1}", incompleteDirectory, directoryNameWithoutInc));
            currDir.Delete(true);
        }


        /// <summary>
        /// просматривает incompleteDirectory для выделения списка папок, начинающихся с inc_ и длинна имени папки >4
        /// </summary>
        /// <returns></returns>
        public static List<DirectoryInfo> GetDownloadingCommonFolder()
        {
            DirectoryInfo incomplete = new DirectoryInfo(incompleteDirectory);
            DirectoryInfo[] mayBeDownloadsFolder = incomplete.GetDirectories();
            List<DirectoryInfo> downloadsFolder = new List<DirectoryInfo>();

            foreach (DirectoryInfo a in mayBeDownloadsFolder)
            {
                if (a.Name.Length >= 4)

                    if ("inc_" == a.Name.Remove(4))
                        downloadsFolder.Add(a);
            }
            return downloadsFolder;
        }

        /// <summary>
        /// просматривает incompleteDirectory для выделения списка папок, чьи имена не начинаются на inc_ или имеющих имя короче 4 символов
        /// </summary>
        /// <returns>List Downloads Folders</returns>
        public static List<DirectoryInfo> GetNoDownloadingCommonFolder()
        {
            DirectoryInfo incomplete = new DirectoryInfo(incompleteDirectory);
            DirectoryInfo[] mayBeDownloadsFolder = incomplete.GetDirectories();
            List<DirectoryInfo> downloadsFolder = new List<DirectoryInfo>();

            foreach (DirectoryInfo a in mayBeDownloadsFolder)
            {
                if (a.Name.Length >= 4)
                {
                    if ("inc_" != a.Name.Remove(4))
                        downloadsFolder.Add(a);
                }
                else
                {
                    downloadsFolder.Add(a);
                }

            }
            return downloadsFolder;
        }

        /// <summary>
        /// Возвращяет номер первой незакачанной части из списка.
        /// </summary>
        /// <param name="partMap">List в котором ищется первый nonDownload элемент</param>
        /// <returns></returns>
        public static int GetNumberNondownloadPart(List<DownloaderPartState> partMap)
        {
            int numberOfPart = partMap.FindIndex(new Predicate<DownloaderPartState>(DelegateSerchNonDownloadPart));
            return numberOfPart;
        }

        /// <summary>
        /// указывает номер первой закачиваемой части.
        /// </summary>
        /// <param name="partMap">List в котором ищется первый downloading элемент</param>
        /// <returns></returns>
        public static int GetNumberDownloadingPart(List<DownloaderPartState> partMap)
        {
            int numberOfNonDownloadingPart = partMap.FindIndex(new Predicate<DownloaderPartState>(DelegateSearchDownloadingPart));
            return numberOfNonDownloadingPart;
        }

        /// <summary>
        /// создает карту загрузок, и проверяет нет ли уже загруженных частей файла
        /// </summary>
        /// <param name="AmountOfPart"></param>
        /// <returns>список состояний загрузки частей файла</returns>
        public List<DownloaderPartState> GetPartMap(int AmountOfPart)
        {
            List<DownloaderPartState> map = new List<DownloaderPartState>();
            GetEmptyList(ref map, AmountOfPart);
            FileInfo[] allParts = GetAllPartsOfFile();
            foreach (FileInfo a in allParts)
            {
                int number = GetNumberOfPart(a);
                map[number] = DownloaderPartState.downloaded;
            }
            return map;
        }

        /// <summary>
        /// заполняет item частями файла, отмечая их как не загруженные
        /// </summary>
        /// <param name="item">List DownloaderPartState  </param>
        /// <param name="size">желаемый int размер item 'а</param>
        private static void GetEmptyList(ref List<DownloaderPartState> item, int size)
        {
            for (int i = 0; i < size; i++)
            {
                item.Add(DownloaderPartState.nonDownloaded);
            }
        }

        /// <summary>
        /// Возвращяет int номер текущей item части
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        private int GetNumberOfPart(FileInfo item)
        {
            String name = item.Name;
            name = name.Remove(0, 5 + GetNameOfLoading().Length);
            int length = name.Length;
            name = name.Remove(length - 4, 4);
            return Int16.Parse(name);
        }

        /// <summary>
        /// возвращяет имя загрузки, удаляя первые четыре символа "inc_" от имени текущей папки
        /// </summary>
        /// <returns>имя загружаемого файла</returns>
        private String GetNameOfLoading()
        {
            String bufer = directory.Name.Remove(0, 4);
            return bufer;
        }

        /// <summary>
        /// Для данной закачки по номеру заданной части, возвращяет FileInfo объект
        /// </summary>
        /// <param name="number"></param>
        /// <returns></returns>
        private FileInfo GetFileInfoByNumber(int number)
        {
            FileInfo fi = new FileInfo(String.Format("{0}/{1}/{2}_part{3}.bin", incompleteDirectory, directory.Name, GetNameOfLoading(), number));
            return fi;
        }

        /// <summary>
        /// возвращяет строку directory, без первых четырех символов.
        /// </summary>
        /// <param name="directory"></param>
        /// <returns>String directory минус первые четыре символа</returns>
        private static String GetNameOfLoadingStatic(String directory)
        {
            String bufer = directory.Remove(0, 4);
            return bufer;
        }

        
        /// <summary>
        /// Если данный файл, является ужа скачанной частью, или сейчас загружаемой, вернет true, иначе false
        /// </summary>
        /// <param name="item">файл в файловой системе</param>
        /// <param name="nameOfDownloading">имя конечного файла</param>
        /// <returns></returns>
        private static bool IsItSignatureFile(FileInfo item, String nameOfDownloading)
        {
            if (item.Name.Length > nameOfDownloading.Length + 5)
            {
                String bufer = item.Name.Remove(nameOfDownloading.Length + 5);
                if (
                    item.Name == String.Format("Loading_{0}.bin", nameOfDownloading) ||
                    (bufer == String.Format("{0}_part", nameOfDownloading) && item.Extension == ".bin"))
                    return true;
                else return false;
            }
            else return false;
        }


        /// <summary>
        /// считывает Loading_ файл для получения информации о загрузке. Если файл ненайден папка удаляется
        /// </summary>
        /// <returns>информация о текущей закачке</returns>
        public Loading ReadInitializingFile()
        {
            String name = GetNameOfLoading();
            BinaryFormatter formatter = new BinaryFormatter();
            try
            {
                //считывает текущий файл информации о загрузке 
                Stream fStream = new FileStream(String.Format("{0}/Loading_{1}.bin", directory.FullName, name), FileMode.Open, FileAccess.Read, FileShare.Read);
                Loading returning = (Loading)formatter.Deserialize(fStream);
                fStream.Close();
                return returning;
            }
            catch (FileNotFoundException e)
            {
                FolderMaster.RemoveDownloadFolder(name);
                throw new FileSystemException();
            }
        }

        /// <summary>
        /// Запись файла конфигурации скачки
        /// </summary>
        /// <param name="writeObject"></param>
        /// <param name="directoryNameWithoutInc"></param>
        private static void WriteInitializeFileStatic(Loading writeObject, String directoryNameWithoutInc)
        {
            BinaryFormatter bf = new BinaryFormatter();
            String myString = String.Format("{0}/inc_{1}/Loading_{2}.bin", incompleteDirectory, directoryNameWithoutInc, writeObject.FileName);
            using (Stream fStream = new FileStream(myString, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                bf.Serialize(fStream, writeObject);
                fStream.Close();
            }
        }


        /// <summary>
        /// Если данная часть не закачана вернет true, иначе false
        /// </summary>
        /// <param name="a"></param>
        /// <returns>bool</returns>
        private static bool DelegateSerchNonDownloadPart(DownloaderPartState a)
        {
            if (a == DownloaderPartState.nonDownloaded)
                return true;
            else return false;
        }

        /// <summary>
        /// Если данная часть сейчас скачивается, вернет true, иначе false
        /// </summary>
        /// <param name="a"></param>
        /// <returns></returns>
        private static bool DelegateSearchDownloadingPart(DownloaderPartState a)
        {
            if (a == DownloaderPartState.downloading)
                return true;
            else return false;
        }

    }

    class FileSystemException : ApplicationException
    {

    }

    class UnforeseenExcption : ApplicationException
    {

    }
}
